package educrypt.cryptanalysis;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import educrypt.analysis.Frequencies;
import educrypt.analysis.FrequencyPair;
import educrypt.exceptions.EduCryptException;
import educrypt.i18n.Messages;
import educrypt.log.ObservableLogger;
import educrypt.log.SimpleLogger;

public class FrequencyBasedCryptAnalysis {
	Frequencies cipherTextFrequencies;
	Frequencies averageFrequencies;

	private final int MAX_FREQUENCY_COINCIDENCES = 4;

	public FrequencyBasedCryptAnalysis(Frequencies cipherTextFrequencies,
			Frequencies averageFrequencies) throws EduCryptException {
		if (cipherTextFrequencies.getMaxNumberOfSamePercentageOcurrences() > MAX_FREQUENCY_COINCIDENCES) {
			throw new EduCryptException(Messages.getString("errCryptanalysis"));
		}
		this.cipherTextFrequencies = cipherTextFrequencies;
		this.averageFrequencies = averageFrequencies;

		logCryptanalysisDetails();
	}

	private void logCryptanalysisDetails() {
		// Log to console
		SimpleLogger logger = ObservableLogger.getInstance();
		logger.log(SimpleLogger.TITLE,
				"---- " + Messages.getString("guiFrequencyCryptanalysis")
						+ " ----");

		logger.log(SimpleLogger.TITLE, Messages.getString("guiTextFrequencies"));
		logger.log(SimpleLogger.CONTENT, this.cipherTextFrequencies.toString());

		logger.log(SimpleLogger.TITLE,
				Messages.getString("guiAverageFrequencies"));
		logger.log(SimpleLogger.CONTENT, this.averageFrequencies.toString());
	}

	public List<Integer> getSortedOffsetsFromCaesarCandidateKeys(
			List<List<String>> candidateKeys) {
		Map<Integer, Integer> candidateOffsets = new HashMap<Integer, Integer>();

		List<String> baseNgrams = Arrays.asList(cipherTextFrequencies
				.getNgrams());

		for (List<String> candidateKey : candidateKeys) {
			int offset;
			int basePos = 0;
			while (basePos < baseNgrams.size()) {
				int newPos = baseNgrams.indexOf(candidateKey.get(basePos));
				if (basePos >= newPos) {
					offset = basePos - newPos;
				} else {
					// size - (newPos - basePos) = size - newPos + basePos
					offset = baseNgrams.size() - newPos + basePos;
				}
				int offsetCount = 0;
				if (candidateOffsets.containsKey(offset)) {
					offsetCount = candidateOffsets.get(offset);
				}
				offsetCount++;
				candidateOffsets.put(offset, offsetCount);
				basePos++;
			}
		}
		List<Integer> sortedCandidateOffsets = sortOffsetByCount(candidateOffsets);

		ObservableLogger.getInstance().log(SimpleLogger.TITLE,
				Messages.getString("guiCryptanalysisOffset"));
		ObservableLogger.getInstance().log(SimpleLogger.CONTENT,
				Integer.toString(sortedCandidateOffsets.get(0)));

		return sortedCandidateOffsets;
	}

	private List<Integer> sortOffsetByCount(
			Map<Integer, Integer> candidateOffset) {
		List<Integer> sortedOffsets = new ArrayList<Integer>();

		List<OffsetCountPair> candidateOffsetCountPair = new ArrayList<OffsetCountPair>();
		for (Entry<Integer, Integer> entry : candidateOffset.entrySet()) {
			candidateOffsetCountPair.add(new OffsetCountPair(entry.getKey(),
					entry.getValue()));
		}
		Collections.sort(candidateOffsetCountPair);
		for (OffsetCountPair pair : candidateOffsetCountPair) {
			sortedOffsets.add(pair.getFirst());
		}
		return sortedOffsets;
	}

	public List<List<String>> cryptAnalyze() {
		List<Frequencies> cipherTextFrequenciesPermutated = this.cipherTextFrequencies
				.getPermutations();
		List<Frequencies> averageFrequenciesPermutated = this.averageFrequencies
				.getPermutations();

		List<List<String>> allCorrespondences = getCorrespondences(
				cipherTextFrequenciesPermutated, averageFrequenciesPermutated);

		// for (List<String> correspondences : allCorrespondences) {
		// ObservableLogger.getInstance().log(correspondences.toString());
		// }
		return allCorrespondences;
	}

	private List<List<String>> getCorrespondences(
			List<Frequencies> cipherTextFrequenciesPermutated,
			List<Frequencies> averageFrequenciesPermutated) {
		List<List<String>> allCorrespondences = new ArrayList<List<String>>();
		for (Frequencies cipherTextFrequencies : cipherTextFrequenciesPermutated) {
			for (Frequencies averageFrequencies : averageFrequenciesPermutated) {
				Map<String, String> unsortedCorrespondences = cipherTextFrequencies
						.getCorrespondences(averageFrequencies);

				// ObservableLogger.getInstance().log(
				// unsortedCorrespondences.toString());

				List<String> sortedCandidateKeys = getSortedCandidateKeys(unsortedCorrespondences);
				allCorrespondences.add(sortedCandidateKeys);
			}
		}
		return allCorrespondences;
	}

	private List<String> getSortedCandidateKeys(
			Map<String, String> unsortedCorrespondences) {
		// Initialize sortedCorrespondences
		List<String> sortedCandidateKey = new ArrayList<String>();
		for (FrequencyPair pair : this.cipherTextFrequencies) {
			sortedCandidateKey.add(unsortedCorrespondences.get(pair
					.getSymbols()));
		}

		return sortedCandidateKey;
	}
}
